lst-tuple-indexing 与 lst-namedtuple,完成正负索引、切片和字段访问。反例:
mutable_inside = ([1],)的内部列表仍可追加元素,且hash(mutable_inside)会抛出TypeError。
| 特性 | 说明 |
|---|---|
| 浅层不可变性 | 创建后外层槽位不可增删或重绑;引用的可变对象仍可变化 |
| 有序性 | 元素按插入顺序存储,可通过索引访问 |
| 异构性 | 可包含不同类型的元素(整数、字符串等) |
| 实现相关的大小 | 同成员引用下 CPython 元组通常较小;仍应在目标环境测量 |
| 可迭代性 | 可用于for循环、推导式等 |
# 方式1: 使用圆括号创建元组
# 圆括号是可选的,但加上可以提高代码可读性
tup1 = ('physics', 'chemistry', 1997, 2000) # 包含字符串和整数的混合元组
tup2 = (1, 2, 3, 4, 5) # 纯整数元组
tup3 = "a", "b", "c", "d" # 不加括号也可以,Python会自动识别为元组
# 创建空元组
# 空元组常用于初始化变量或作为函数默认参数
empty_tup = ()
# 创建单元素元组(注意逗号!)
# 这是一个常见的陷阱:单个元素后面必须有逗号,否则会被识别为带括号的表达式
single_tup = (50,) # 正确:有逗号,是元组
not_a_tuple = (50) # 错误:没有逗号,这只是整数50
# 打印结果以验证类型
print('元组1:', tup1)
print('元组2:', tup2)
print('元组3:', tup3) # 证明不加括号也能创建元组
print('空元组:', empty_tup)
print('单元素元组:', single_tup)元组1: ('physics', 'chemistry', 1997, 2000)
元组2: (1, 2, 3, 4, 5)
元组3: ('a', 'b', 'c', 'd')
空元组: ()
单元素元组: (50,)
(50,) → 这是一个元组(有逗号)(50) → 这只是整数50(没有逗号)从列表: (1, 2, 3, 4)
从字符串: ('H', 'e', 'l', 'l', 'o')
从range: (0, 1, 2, 3, 4)
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 已知元素,数量少 | tup = (1, 2) |
简洁直观 |
| 从其他序列转换 | tuple(seq) |
清晰表达意图 |
| 需要计算生成元素 | tuple(x for x in ...) |
内存高效 |
| 需要空元组 | empty = () |
最简洁 |
# 创建示例元组
tup1 = ('physics', 'chemistry', 1997, 2000) # 混合类型元组
tup2 = (1, 2, 3, 4, 5, 6, 7) # 纯数字元组
# 正向索引:从0开始,从左向右数
print('tup1[0]:', tup1[0]) # 第一个元素:'physics'
print('tup1[1]:', tup1[1]) # 第二个元素:'chemistry'
# 负向索引:从-1开始,从右向左数
print('tup2[-1]:', tup2[-1]) # 最后一个元素:7
print('tup2[-2]:', tup2[-2]) # 倒数第二个元素:6
# 切片操作:获取子元组
# 语法:tup[start:end:step]
# start包含,end不包含(左闭右开区间)
print('tup2[1:5]:', tup2[1:5]) # 索引1到4的元素:(2, 3, 4, 5)
print('tup2[::2]:', tup2[::2]) # 每隔一个取一个:(1, 3, 5, 7)
print('tup2[::-1]:', tup2[::-1]) # 反转元组:(7, 6, 5, 4, 3, 2, 1)tup1[0]: physics
tup1[1]: chemistry
tup2[-1]: 7
tup2[-2]: 6
tup2[1:5]: (2, 3, 4, 5)
tup2[::2]: (1, 3, 5, 7)
tup2[::-1]: (7, 6, 5, 4, 3, 2, 1)
| 语法 | 含义 | 示例 |
|---|---|---|
[start:end] |
从start到end-1 | tup[1:5] |
[start:end:step] |
按步长取元素 | tup[::2] |
[:end] |
从头开始 | tup[:3] |
[start:] |
到结尾 | tup[2:] |
[::-1] |
反转序列 | tup[::-1] |
IndexErrortry/except 或先检查 len() 来避免# 创建一个元组
tup = (12, 34.56)
# 尝试修改元组的第一个元素
try:
tup[0] = 20 # 这会抛出TypeError
except TypeError as e:
print(f'错误类型: {type(e).__name__}')
print(f'错误信息: {e}')
print('结论:元组外层槽位一旦创建就不能重绑') # 该报错不证明内部可变对象被固定
# 虽然不能修改,但可以连接两个元组创建新元组
# 注意:这不是修改原元组,而是创建了一个全新的元组
tup3 = tup + ('abc', 'xyz')
print('原元组:', tup) # 原元组保持不变
print('新元组:', tup3) # 这是一个新创建的元组
# 也可以通过重复创建新元组
tup4 = tup * 2 # 元组重复
print('重复元组:', tup4)错误类型: TypeError
错误信息: 'tuple' object does not support item assignment
结论:元组外层槽位一旦创建就不能重绑
原元组: (12, 34.56)
新元组: (12, 34.56, 'abc', 'xyz')
重复元组: (12, 34.56, 12, 34.56)
| 理由 | 说明 |
|---|---|
| 误改边界 | 只固定外层槽位;完整性另靠校验、授权、版本和所有权 |
| 多线程环境 | 槽位不可变不保证复合操作线程安全;共享状态按协议加锁 |
| 哈希要求 | 键必须可哈希;含列表等不可哈希元素的元组不能作键 |
| 函数式编程 | 使程序更易于推理和测试 |
| 内存优化 | Python可对相同内容的元组共享内存 |
# 定义交易策略的核心参数
# 使用元组避免外层参数被意外改动;实际设置仍需检查、授权并记录版本
trading_params = (
100000, # 初始资金(元)
0.05, # 预期年收益率
30, # 投资期限(年)
0.6 # 股票配置比例
)
# 元组解包:将元组的元素分别赋值给变量
# 这种写法比分别访问元组元素更清晰
initial_capital, rate, years, stock_ratio = trading_params
# 格式化输出参数
# 使用f-string进行格式化,提高可读性
print(f'初始资金: {initial_capital:,.0f}元') # 添加千位分隔符
print(f'年利率: {rate:.1%}') # 百分比格式
print(f'投资期限: {years}年')
print(f'股票配置: {stock_ratio:.0%}')
# 计算期末资产
final_amount = initial_capital * (1 + rate) ** years
print(f'期末资产(复利): {final_amount:,.2f}元')初始资金: 100,000元
年利率: 5.0%
投资期限: 30年
股票配置: 60%
期末资产(复利): 432,194.24元
# 创建价格查找表
# 键是(股票代码, 日期)的元组,值是收盘价
price_lookup = {
('600519.SH', '2024-01-15'): 1856.00,
('600519.SH', '2024-01-16'): 1862.50,
('000858.SZ', '2024-01-15'): 158.20,
('000858.SZ', '2024-01-16'): 159.80
}
# 查询特定股票在特定日期的价格
stock = '600519.SH'
date = '2024-01-15'
price = price_lookup.get((stock, date))
if price:
print(f'{stock} 在 {date} 的收盘价是 {price:.2f}元')
else:
print('未找到匹配的价格数据')
# 字典键必须可哈希;元组仅在每个成员及其嵌套成员均可哈希时才能作为键
# 下面的代码会报错,因为列表是可变的:
# error_dict = {['600519.SH', '2024-01-15']: 1856.00} # TypeError600519.SH 在 2024-01-15 的收盘价是 1856.00元
def calculate_return_metrics(prices):
'''
计算价格序列的多个收益率指标
参数:
prices: 价格列表
返回:
(总收益率, 年化收益率, 波动率)的元组
'''
import numpy as np
# 计算简单收益率
returns = np.diff(prices) / prices[:-1]
# 总收益率
total_return = (prices[-1] - prices[0]) / prices[0]
# 年化收益率(假设252个交易日)
annual_return = (1 + total_return) ** (252 / len(prices)) - 1
# 年化波动率
annual_volatility = returns.std() * np.sqrt(252)
return total_return, annual_return, annual_volatility
# 使用示例
stock_prices = [100, 102, 101, 105, 108, 107, 110]
total_ret, ann_ret, ann_vol = calculate_return_metrics(stock_prices)
print(f'总收益率: {total_ret:.2%}')
print(f'年化收益率: {ann_ret:.2%}')
print(f'年化波动率: {ann_vol:.2%}')总收益率: 10.00%
年化收益率: 2991.27%
年化波动率: 30.26%
# 定义一个时间戳数据点
tick = ('600519.SH', '2024-01-15 09:30:00', 1856.00, 100)
# 格式:(股票代码, 时间戳, 价格, 成交量)
# 元组解包提取字段
symbol, timestamp, price, volume = tick
print(f'股票代码: {symbol}')
print(f'时间戳: {timestamp}')
print(f'价格: {price:.2f}元')
print(f'成交量: {volume}手')
# 将多个tick数据组成列表
ticks = [
('600519.SH', '09:30:00', 1856.00, 100),
('600519.SH', '09:31:00', 1858.50, 150),
('600519.SH', '09:32:00', 1855.00, 80),
]
# 分析第一分钟的价格变化
price_change = ticks[1][2] - ticks[0][2]
print(f'第一分钟价格变化: {price_change:+.2f}元')股票代码: 600519.SH
时间戳: 2024-01-15 09:30:00
价格: 1856.00元
成交量: 100手
第一分钟价格变化: +2.50元
| 选择元组 | 选择列表 |
|---|---|
| 外层槽位需固定,内层对象另行管理 | 数据会频繁增删 |
| 所有成员及嵌套成员均可哈希且需要复合键 | 逐步构建数据序列 |
| 在目标环境实测后满足大小/时间约束 | 在目标环境实测后更符合实际负载 |
| 函数返回多个值 | 需要排序、追加等操作 |
| 固定的记录结构 | 动态变化的集合 |
import sys
import timeit
# 创建相同内容的元组和列表
tup = tuple(range(1000))
lst = list(range(1000))
# 内存占用比较
print(f'元组内存占用: {sys.getsizeof(tup)} 字节')
print(f'列表内存占用: {sys.getsizeof(lst)} 字节')
print(f'元组更节省: {sys.getsizeof(lst) - sys.getsizeof(tup)} 字节')
# 在同一进程做访问时间微基准;结果只代表本次环境与负载
tup_time = timeit.timeit('x = tup[500]', globals=globals(), number=1000000)
lst_time = timeit.timeit('x = lst[500]', globals=globals(), number=1000000)
print(f'元组访问时间: {tup_time:.4f}秒')
print(f'列表访问时间: {lst_time:.4f}秒')
print(f'本次元组相对列表时间差: {(lst_time/tup_time - 1)*100:.1f}%') # 正值表示本次元组更快,负值表示更慢元组内存占用: 8040 字节
列表内存占用: 8056 字节
元组更节省: 16 字节
元组访问时间: 0.0251秒
列表访问时间: 0.0261秒
本次元组相对列表时间差: 3.9%
59823756 的一次本机运行得到元组 0.0292s、列表 0.0240s、相对差 -17.5%;该次结果不支持“元组访问更快”。from collections import namedtuple
# 定义一个命名元组类型
# 相当于创建了一个简单的类
Stock = namedtuple('Stock', ['symbol', 'price', 'volume'])
# 创建Stock实例
stock1 = Stock('600519.SH', 1856.00, 1000)
stock2 = Stock(symbol='000858.SZ', price=158.20, volume=5000)
# 可以像元组一样索引访问
print(f'股票代码(索引): {stock1[0]}')
# 也可以像对象一样属性访问
print(f'股票代码(属性): {stock1.symbol}')
print(f'价格: {stock1.price}')
print(f'成交量: {stock1.volume}')
# 命名元组仍是元组,只保证外层槽位不可重绑
print(f'是否为元组: {isinstance(stock1, tuple)}')
# 命名元组有友好的字符串表示
print(f'股票信息: {stock1}')股票代码(索引): 600519.SH
股票代码(属性): 600519.SH
价格: 1856.0
成交量: 1000
是否为元组: True
股票信息: Stock(symbol='600519.SH', price=1856.0, volume=1000)
name、code、IPO_date、exchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号" # 设置名称为"中国通号"
print('name:',type(name)) # 输出name
code = "688009" # 设置代码为"688009"
print('code:',type(code)) # 输出code
IPO_date = "2019年7月22日" # 设置日期为"2019年7月22日"
print('IPO_data:',type(IPO_date)) # 输出IPO_data
exchange = "上海证券交易所" #以字符串输入中国通号A股的上市交易所
print('exchange',type(exchange)) #使用type函数输出数据类型
capital = 10589819000 # 总股本:10589819000股
print('capital',type(capital)) # 输出capital
shares = 8621018000 # 流通股本:8621018000股
print('share:',type(shares)) # 输出share
price_IPO = 5.85 #以浮点型输入中国通号A股发行价
print('price_IPO:',type(price_IPO)) #使用type函数输出数据类型并使用print函数打印
price_Jul22 = 12.27 # 设置当前价格为12.27
print('price_Jul22:',type(price_Jul22)) # 输出price_Jul22
change_Jul22 = 1.09744 # 涨幅:109.744%
print('change_Jul22:',type(change_Jul22)) # 输出change_Jul22运行后核对:核对 name、code、IPO_date、exchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
name、code、IPO_date、exchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。tup 的结果;同时写出方向、数量级或表格/图形结构。# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号" # 设置名称为"中国通号"
code = "688009" # 设置代码为"688009"
IPO_date = "2019年7月22日" # 设置日期为"2019年7月22日"
exchange = "上海证券交易所" # 设置交易所为"上海证券交易所"
capital = 10589819000 # 总股本:10589819000股
shares = 8621018000 # 流通股本:8621018000股
price_IPO = 5.85 # 设置当前价格为5.85
price_Jul22 = 12.27 # 设置当前价格为12.27
change_Jul22 = 1.09744 # 涨幅:109.744%
tup =(name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22) #创建一个元组,包括以上数据
print(tup) #打印输出这个元组
print(tup[0]) #访问该元组的首个元素
print(tup[-1]) #访问该元组的末尾元素
print(tup[2:6])#访问该元组的第3个至第6个元素运行后核对:核对 name、code、IPO_date、exchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
name、code、IPO_date、exchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。tup_new 的结果;同时写出方向、数量级或表格/图形结构。# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号" # 设置名称为"中国通号"
code = "688009" # 设置代码为"688009"
IPO_date = "2019年7月22日" # 设置日期为"2019年7月22日"
exchange = "上海证券交易所" # 设置交易所为"上海证券交易所"
capital = 10589819000 # 总股本:10589819000股
shares = 8621018000 # 流通股本:8621018000股
price_IPO = 5.85 # 设置当前价格为5.85
price_Jul22 = 12.27 # 设置当前价格为12.27
change_Jul22 = 1.09744 # 涨幅:109.744%
tup = (name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22) # 定义元组tup
price_Jul22_H = 5.43 #以浮点型输入中国通号H股7月22日收盘价
change_Jul22_H = -0.1171 #以浮点数输入中国通号H股7月22日涨跌幅
del tup #删除任务2中创建的元组
tup_new = (name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22,price_Jul22_H,change_Jul22_H) #创建包含H股股价和涨跌幅的新元组
print(tup_new) #打印这个新元组运行后核对:核对 name、code、IPO_date、exchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
lst-tuple-indexing 与 lst-namedtuple,完成正负索引、切片和字段访问。HASH_STOP:TypeError 和中国债券拓展应用记录。hash(mutable_inside),确认程序能够捕获 TypeError。name, code, ipo_date, exchange;拓展应用记录 bond_name, bond_code, issuer, maturity_date, coupon_rate;两者字段顺序固定。from typing import NamedTuple # 导入命名元组以建立可按字段访问的固定记录
class Listing(NamedTuple): # 导入命名元组以建立可按字段访问的固定记录
name:str; code:str; ipo_date:str; exchange:str # 执行当前步骤并保留可追踪结果
listing_record=Listing('中国通号','688009.SH','2019-07-22','上海证券交易所') # 构造带字段名的上市记录元组
assert listing_record[:2]==('中国通号','688009.SH') and listing_record.code=='688009.SH' # 构造带字段名的上市记录元组
hashable_key=(listing_record.code,listing_record.ipo_date); company_mapping={hashable_key:listing_record.name} # 构造带字段名的上市记录元组
class BondRecord(NamedTuple): # 定义可按字段访问的中国债券新案例结构
bond_name:str; bond_code:str; issuer:str; maturity_date:str; coupon_rate:float # 固定债券识别与条款字段顺序
bond_record=BondRecord('24国债02','019733','中华人民共和国财政部','2025-01-25',1.96) # 依据上交所通知构造另一笔中国国债记录
bond_slice=bond_record[:2] # 用切片提取债券简称和代码
assert bond_slice==('24国债02','019733') and bond_record.coupon_rate==1.96 # 核对债券新案例的切片与字段访问
mutable_inside=([],) # 构造含列表元组以说明不可哈希边界
hash_diagnostic='HASH_CHECK_FAILED' # 预设失败状态以防异常检查被跳过
try: # 实际执行含可变成员元组的哈希检查
hash(mutable_inside) # 满足条件列表成员不可哈希的边界
except TypeError: # 只捕获教学预期的不可哈希异常
hash_diagnostic='HASH_STOP:TypeError' # 生成跨环境稳定的停止依据
if hash_diagnostic!='HASH_STOP:TypeError': # 防止检查意外通过却继续写出结果
raise AssertionError('停止:含可变成员的元组未满足条件TypeError') # 显式暂停不符合边界的结果
print(listing_record,listing_record[-1],company_mapping,hash_diagnostic) # 输出字段、映射与哈希停止依据
print(bond_record,bond_record.maturity_date,bond_slice) # 输出债券新案例记录、字段访问与切片依据BondRecord,用字段固定债券简称、代码、发行人、到期日与票面利率;第二条实例为中国“24国债02”,并以字段访问和切片核对。条款依据上海证券交易所上市通知。HASH_STOP:TypeError,以及债券记录、到期日字段和债券简称/代码切片。另行核对:代码必须实际执行 hash(mutable_inside) 并捕获 TypeError;债券字段与切片检查也必须通过。[商业大数据分析与应用]